package com.yunzhou.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.yunzhou.constants.Constants;
import com.yunzhou.constants.RedisConstants;
import com.yunzhou.entity.UserInfo;
import com.yunzhou.entity.UserMessage;
import com.yunzhou.enums.UserMessageStatusEnum;
import com.yunzhou.enums.UserMessageTypeEnum;
import com.yunzhou.enums.UserStatusEnum;
import com.yunzhou.mapper.ForumArticleMapper;
import com.yunzhou.mapper.LikeRecordMapper;
import com.yunzhou.mapper.UserMapper;
import com.yunzhou.mapper.UserMessageMapper;
import com.yunzhou.properties.EmailProperties;
import com.yunzhou.properties.JwtProperties;
import com.yunzhou.service.UserService;
import com.yunzhou.utils.JwtUtil;
import com.yunzhou.utils.Md5Util;
import com.yunzhou.utils.MyStringUtil;
import com.yunzhou.vo.UserDetailVO;
import com.yunzhou.vo.UserLoginVO;
import jakarta.mail.MessagingException;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate; // redis
    @Autowired
    private JwtProperties jwtProperties; // jwt配置类
    @Autowired
    private EmailProperties emailProperties; // 邮件配置类
    @Autowired
    private JavaMailSender javaMailSender; // 发送邮件类
    @Autowired
    private ForumArticleMapper forumArticleMapper;
    @Autowired
    private LikeRecordMapper likeRecordMapper;
    @Autowired
    private UserMessageMapper userMessageMapper;

    @Override
    public UserInfo selectByUserId(Long userId) {
        return userMapper.selectByUserId(userId);
    }

    @Override
    public UserInfo selectByEmail(String email) { return userMapper.selectByEmail(email); }

    @Override
    public UserInfo selectByNickname(String nickname) { return userMapper.selectByNickname(nickname); }

    // 登录
    @Override
    public UserLoginVO login(HttpSession session, String email, String password, String imageCheckCode) {

        // 先根据用户邮箱获取用户
        UserInfo userInfo = userMapper.selectByEmail(email);

        // 判断图片验证码是否匹配 判断验证码时忽略大小写 equalsIgnoreCase
        System.out.println("本次登录的session中的图片验证码为" + session.getAttribute(Constants.LOGIN_IMAGE_CHECK_CODE));
        if (session.getAttribute(Constants.LOGIN_IMAGE_CHECK_CODE) == null) {
            throw new RuntimeException("图片验证码已过期");
        }
        if (!imageCheckCode.equalsIgnoreCase((String)session.getAttribute(Constants.LOGIN_IMAGE_CHECK_CODE))){
            throw new RuntimeException("图片验证码不匹配");
        }

        // 判断用户是否存在数据库中
        if (userInfo == null) {
            throw new RuntimeException("用户不存在");
        }

        // 判断密码是否正确
        if (!userInfo.getPassword().equals(Md5Util.getMD5String(password))) {
            throw new RuntimeException("密码错误");
        }

        // 判断用户状态是否被禁用
        if (userInfo.getStatus().equals(UserStatusEnum.DISABLE.getStatus())) {
            throw new RuntimeException("用户已被禁用");
        }

        // 生成token，将userId存储到Token中
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userInfo.getUserId());
        String token = JwtUtil.createToken(jwtProperties.getSecretKey(), jwtProperties.getTtl(), claims);

        // 将token存储到redis中
        // 这里token为key，value为UserLoginVO，过期时间与token过期时间一致:24h
        UserLoginVO userLoginVO = BeanUtil.copyProperties(userInfo, UserLoginVO.class);
        userLoginVO.setToken(token);
        Map<String, Object> userMap = new HashMap<>();
        userMap.put("userId", userLoginVO.getUserId().toString());
        userMap.put("nickname", userLoginVO.getNickname());
        userMap.put("avatar", userLoginVO.getAvatar());
        userMap.put("token", userLoginVO.getToken());
//        Map<String, Object> userMap = BeanUtil.beanToMap(
//                userLoginVO,
//                new HashMap<>(),
//                CopyOptions.create() // 数据拷贝时的选项
//                    // 1、忽略值为null的数据
//                    .setIgnoreNullValue(true)
//                    // 2、参数：字段名和字段值，返回值是修改后的字段值。将value字段值转为字符串
//                    .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        String tokenKey = RedisConstants.LOGIN_TOKEN_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // stringRedisTemplate.opsForValue().set(token, token,jwtProperties.getTtl() / 1000, TimeUnit.SECONDS);

        // 设置Redis中Token的有效期（就是设置作为key的token的过期时间，过期就从Redis中删除，节省内存空间）
        stringRedisTemplate.expire(tokenKey, jwtProperties.getTtl() / 1000, TimeUnit.SECONDS);

        // 更新用户登录时间
        userInfo.setLastLoginTime(LocalDateTime.now());
        userInfo.setLastLoginIp("未知");
        userInfo.setLastLoginIpAddress("未知");
        userMapper.update(userInfo);

        // 用户登录成功后，删除掉session中的图片验证码
        session.removeAttribute(Constants.LOGIN_IMAGE_CHECK_CODE);

        // 返回给前端用户id，用户昵称，token
        return userLoginVO;
    }

    // 发送邮件（注册用）
    @Override
    public void sendRegisterEmailCheckCode(HttpSession session, String email, String imageCheckCode) {
        UserInfo userInfo = userMapper.selectByEmail(email);
        if (userInfo != null) {
            throw new RuntimeException("邮箱已被注册过！");
        }

        // 生成随机字符串
        String code = MyStringUtil.getRandomString(5);
        System.out.println("本次随机生成的邮件验证码为："+code);

        // 发送邮件
        sendEmail(code,email);

        // 发送邮件成功后，将邮箱key，邮件内容value存储到redis中
        // 这里我打算把验证码保存到redis中，设置过期时间
        // 防止用户多次发送验证码，每发送一次验证码都需要把redis中的上一个覆盖
        // 我打算拿用户邮箱作为key，验证码为value，存入redis中，设置过期时间为5分钟
        // 当用户注册后，图片验证码失效（redis + session 都失效）
        stringRedisTemplate.opsForValue().set(email, code, 5, TimeUnit.MINUTES);
        String email123 = stringRedisTemplate.opsForValue().get(email);
        System.out.println("注册邮箱存储到redis中，key：" + email + "value：" + email123);

        // 邮件发送成功后，删除session中的发送邮件用图片验证码
        session.removeAttribute(Constants.REGISTER_SEND_EMAIL_IMAGE_CHECK_CODE);
    }

    // 注册
    @Override
    @Transactional
    public void register(HttpSession session, String email, String emailCheckCode, String password, String imageCheckCode) {
        // 查看当前邮箱是否已被使用
        UserInfo userInfo = userMapper.selectByEmail(email);
        if (userInfo != null) {
            throw new RuntimeException("当前邮箱已被注册");
        }

        // 图形验证码校验
        // 判断图片验证码是否匹配 判断验证码时忽略大小写 equalsIgnoreCase
        if (!imageCheckCode.equalsIgnoreCase((String)session.getAttribute(Constants.REGISTER_IMAGE_CHECK_CODE))){
            throw new RuntimeException("图片验证码不匹配");
        }

        // 从redis中取出key为email，value为邮箱验证码，与前端传递过来的验证码匹配
        String redisEmailCheckCode = stringRedisTemplate.opsForValue().get(email);
        if (redisEmailCheckCode == null) {
            throw new RuntimeException("邮箱验证码已过期");
        }
        if (!redisEmailCheckCode.equalsIgnoreCase(emailCheckCode)) {
            throw new RuntimeException("邮箱验证码不匹配");
        }

        // 插入数据库（主键自增），密码使用MD5加密
        UserInfo u = UserInfo
                .builder()
                .email(email)
                .password(Md5Util.getMD5String(password))
                .joinTime(LocalDateTime.now())
                .currentIntegral(0) // 当前积分
                .totalIntegral(0) // 总积分
                .status(UserStatusEnum.ENABLE.getStatus()) // 枚举类
                .build();
        userMapper.insert(u);

        // 更新用户昵称
        Long userId = u.getUserId();
        u.setNickname("云舟用户_" + userId);
        userMapper.update(u);

        // 当用户注册成功后，发送一条系统消息：欢迎注册
        UserMessage userMessage = UserMessage.builder()
                .receivedUserId(u.getUserId())
                .sendUserId(1L)
                .sendNickname("系统管理员")
                .messageType(UserMessageTypeEnum.SYS.getMessageType()) // 0：系统消息
                .messageContent("欢迎使用云舟~") // 欢迎注册
                .status(UserMessageStatusEnum.NO_READ.getStatus()) // 消息状态为：未读
                .createTime(LocalDateTime.now())
                .build();
        userMessageMapper.insert(userMessage);

        // 注册成功，更新用户的积分 +5
        // updateUserIntegral(u.getUserId(), IntegralOperateTypeEnum.REGISTER, IntegralChangeTypeEnum.ADD, 5);

        // 让redis中的邮箱验证码失效
        stringRedisTemplate.delete(email);

        // 让session中的图片验证码失效
        session.removeAttribute(Constants.REGISTER_IMAGE_CHECK_CODE);
    }

    // 封装发送邮件方法
    private void sendEmail(String code, String receiver){
        // 读取配置文件中的发件人
        String sendMailer = emailProperties.getUsername();
        System.out.println("发件人为："+emailProperties.getUsername());

        try {
            //true 代表支持复杂的类型
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
            mimeMessageHelper.setFrom(sendMailer); //邮件发信人
            mimeMessageHelper.setTo(receiver); //邮件收信人
            mimeMessageHelper.setSubject("云舟注册-邮箱验证码"); //邮件标题
            mimeMessageHelper.setText("云舟注册-所需的验证码为：" + code + "，有效时间为 5 分钟"); //邮件内容
            mimeMessageHelper.setSentDate(new Date()); //邮件发送时间
            javaMailSender.send(mimeMessageHelper.getMimeMessage()); //发送邮件
            System.out.println("发送邮件成功：" +sendMailer+"===>"+receiver);
        } catch (MessagingException e) {
            e.printStackTrace();
            System.out.println("发送邮件失败："+e.getMessage());
            throw new RuntimeException("邮件发送失败");
        }
    }

    // 获取用户详细信息
    @Override
    public UserDetailVO getUserDetail(Long userId) {
        UserInfo userInfo = userMapper.selectByUserId(userId);
        Integer likeCount = likeRecordMapper.getLikeCountByUserId(userId); // 获赞数
        Integer postCount = forumArticleMapper.getPostCountByUserId(userId);// 发表文章数

        UserDetailVO userDetailVO = new UserDetailVO();
        userDetailVO.setUserId(userInfo.getUserId());
        userDetailVO.setNickname(userInfo.getNickname());
        userDetailVO.setEmail(userInfo.getEmail());
        userDetailVO.setPassword(userInfo.getPassword());
        userDetailVO.setSex(userInfo.getSex());
        userDetailVO.setAvatar(userInfo.getAvatar());
        userDetailVO.setPersonDescription(userInfo.getPersonDescription());
        userDetailVO.setJoinTime(userInfo.getJoinTime());
        userDetailVO.setLastLoginTime(userInfo.getLastLoginTime());
        userDetailVO.setLastLoginIp(userInfo.getLastLoginIp());
        userDetailVO.setLastLoginIpAddress(userInfo.getLastLoginIpAddress());
        userDetailVO.setCurrentIntegral(userInfo.getCurrentIntegral());
        userDetailVO.setTotalIntegral(userInfo.getTotalIntegral());
        userDetailVO.setStatus(userInfo.getStatus());

        userDetailVO.setLikeCount(likeCount);
        userDetailVO.setPostCount(postCount);

        return userDetailVO;
    }

    // 修改用户信息
    @Override
    public void updateUserInfo(UserInfo userInfo) {
        userMapper.update(userInfo);
    }

}
